/**
 * 
 * @Project le cOeur - Data Virtualization
 * @Date 08.12.2012
 * @Author Aebi Pascal, Kocher Marc
 * @School BFH-TI Biel, Bachelor Thesis
 *
 */

package main.framework;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.Socket;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;
import java.util.logging.Logger;

import javax.net.ssl.SSLException;

import main.framework.http.ContentType;
import main.framework.http.MessageBody;
import main.framework.http.MessageHeader;
import main.framework.http.Request;
import main.framework.http.Response;

/**
 * The class Worker receives a HTTP requests, processes this request and sends back a corresponding HTTP response.
 * The whole process is done as thread.
 */
public class Worker implements Runnable {

	private Socket socket;
	private RequestHandler requestHandler;
	private Logger logger;
	
	/**
	 * Creates a worker to process a HTTP request.
	 * 
	 * @param socket The socket.
	 * @param requestHandler The request handler.
	 */
	public Worker(Socket socket, RequestHandler requestHandler) {
		this.socket = socket;
		this.requestHandler = requestHandler;
		this.logger = Logger.getLogger("SysLogger");
	}
	
	@Override
	public void run() {
		try {
			// Receive request
			Request request = this.receiveRequest();
			// Log the connection
			this.logger.info(this.socket.toString());
			// Log the request
			this.logger.info(request.getStartLine().toString());
			// Handle request
			Response response = this.requestHandler.handleRequest(request);
			// Send response
			this.sendResponse(response);
		} catch (SSLException e) {
			this.logger.info("Client (" + this.socket.getInetAddress() + ") error: " + e.getMessage());
		} catch (IOException e) {
			this.logger.info(e.getMessage());
		} finally {
			try {
				this.socket.close();
			} catch (IOException e) {
				this.logger.info(e.getMessage());
			}
		}
	}
	
	/**
	 * Receives incoming data from the socket, parses them and returns a HTTP request object.
	 * 
	 * @return A HTTP request.
	 * @throws IOException If an I/O error occurs.
	 * @throws SSLException If a SSL error occurs.
	 */
	private Request receiveRequest() throws IOException, SSLException {
		if(!this.socket.isClosed()){
			InputStream inputStream = this.socket.getInputStream();
			InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
			BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
			Request request = null;
			String line;
			int length = 0;
			boolean receivingRequest = true;
			boolean isHeader = false;
			// Receive data and compose request
			while(receivingRequest){
				line = bufferedReader.readLine();
				if (line == null){
					throw new IOException("End of input stream is reached.");
				} 
				else if(line.isEmpty()){
					isHeader = false;
					request.parse();
					// Check content length and type
					MessageHeader contentLength = request.getMessageHeader("Content-Length");
					MessageHeader contentType = request.getMessageHeader("Content-Type");
					if(contentLength != null && contentType != null){
						length = Integer.parseInt(contentLength.getFieldValue());
						// Read and set data
						if(length > 0){
							char[] messageBody = new char[length];
							bufferedReader.read(messageBody, 0, length);
							request.addMessageBody(new MessageBody(new String(messageBody), new ContentType(contentType.toString())));
						}
					}
					// Check if date is set in request
					if(request.getMessageHeader("Date") == null){
						SimpleDateFormat simpleDateFormat = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.ENGLISH);
						simpleDateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
						request.addMessageHeader(new MessageHeader("Date", simpleDateFormat.format(new Date())));
					}
					receivingRequest = false;
				}
				else if(isHeader){
					request.addMessageHeader(new MessageHeader(line));
				}
				else{
					request = new Request(line);
					isHeader = true;
				}
			}
			return request;
		}
		else{
			return null;
		}
	}
	
	/**
	 * Sends a given HTTP response over the socket.
	 * 
	 * @param response The HTTP response.
	 * @throws IOException If an I/O error occurs.
	 */
	private void sendResponse(Response response) throws IOException {
		if(!this.socket.isClosed()){
			OutputStream outputStream = this.socket.getOutputStream();
			OutputStreamWriter outputStreamWriter = new OutputStreamWriter(outputStream);
			BufferedWriter bufferedWriter = new BufferedWriter(outputStreamWriter);
			bufferedWriter.write(response.toString());
			bufferedWriter.flush();
		}
	}

}
